package eu.winterwheels.enterprise.controller.security;

import java.io.IOException;
import java.util.logging.Logger;

import javax.annotation.PostConstruct;
import javax.enterprise.inject.Produces;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.RequestScoped;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import eu.winterwheels.enterprise.model.User;
import eu.winterwheels.enterprise.service.security.LoginService;

@ManagedBean
@RequestScoped
public class LoginController {

	@Inject
	private Logger log;

	@Inject
	private FacesContext facesContext;

	@Inject
	private LoginService loginService;

	/**
	 * This user holds the user credentials returned from the database AFTER
	 * {@link #login()} has been executed.
	 */
	private User user;
	/**
	 * This user holds the temporary login credentials - simply used before
	 * {@link #login()} has been invoked
	 */
	private User tmpUser;

	@Pattern(regexp = "(^[\\w\\-]([\\.\\w])+[\\w]+@([\\w\\-]+\\.)+[a-zA-Z]{2,4}$)|(^admin$)", message = "Bad formed email.")
	private String username;
	@Size(min = 6, max = 20, message = "Password must match the length of at least 6 characters.")
	private String password;

	private boolean isLoggedIn = false;

	@ManagedProperty(value = "#{param.requestedURL}")
	private String requestedURL;

	@PostConstruct
	public void init() {
		// Create new tmp user;
		tmpUser = new User();
		// If user is in session, populate the user instance.
		if (facesContext.getExternalContext().getSessionMap().get("user") != null)
			user = (User) facesContext.getExternalContext().getSessionMap().get("user");
	}

	@Produces
	@Named
	public User getNewUser() {
		return tmpUser;
	}

	@Produces
	@Named
	public User getUser() {
		return user;
	}

	/**
	 * Store the user in a local field AND in the session map, too.
	 * 
	 * @param User
	 *            The user to hold during the session duration.
	 */
	public void setUser(User user) {
		this.user = user;
		facesContext.getExternalContext().getSessionMap().put("user", user);
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password.trim();
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public boolean isLoggedIn() {
		if (facesContext.getExternalContext().getSessionMap().get("user") != null)
			isLoggedIn = true;
		else
			isLoggedIn = false;
		return isLoggedIn;
	}

	public void setLoggedIn(boolean isLoggedIn) {
		this.isLoggedIn = isLoggedIn;
	}

	public String getRequestedURL() {
		return requestedURL;
	}

	public void setRequestedURL(String requestedURL) {
		this.requestedURL = requestedURL;
	}

	/**
	 * The logout conducts 3 tasks:
	 * <ol>
	 * <li>Set the loggedIn flag to false.</li>
	 * <li>Nulls the field-stored user object and creates a newUser.</li>
	 * <li>Invokes the logout method from the request object.</li>
	 * <li>Clears the session.</li>
	 * </ol>
	 */
	public void logout() {
		setLoggedIn(false);
		user = null;
		tmpUser = new User();

		HttpServletRequest request = (HttpServletRequest) facesContext.getExternalContext()
				.getRequest();
		try {
			request.logout();
		} catch (ServletException e) {
			log.warning("Failed to logout with username (" + username + ") and password. "
					+ e.getMessage());
			facesContext.addMessage(null, new FacesMessage("Logout failed."));
		}

		facesContext.getExternalContext().getSessionMap().clear();
	}

	/**
	 * The login conducts 3 tasks:
	 * <ol>
	 * <li>Invoke the login method of the request object.</li>
	 * <li>Set the user to the field variable.</li>
	 * <li>Do the login check again thru EJB.</li>
	 * </ol>
	 * 
	 */
	public String login() {
		HttpServletRequest request = (HttpServletRequest) facesContext.getExternalContext()
				.getRequest();

		log.fine("A login request was issued from " + request.getRemoteHost() + ":"
				+ request.getRemotePort());

		try {
			request.login(getUsername(), getPassword());
			if (request.isUserInRole("admin"))
				log.info("The logged in user is an administrator (admin)");
			else
				log.info("The logged in user is NOT an administrator (admin)");

		} catch (ServletException e) {
			log.warning("Failed to login with username (" + username + ") and password. "
					+ e.getMessage());
			facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_WARN,
					"Login failed when tying to authenticate.", null));
			// If this fails, return null.
			return null;
		}

		// Call business method to check credentials
		getNewUser().setUsername(getUsername());
		getNewUser().setPassword(getPassword());
		user = loginService.login(getNewUser());
		boolean loginOk = false;
		if (user == null) {
			loginOk = false;
		} else {
			loginOk = true;
			// Save the user class in the session map.
			setUser(user);
		}

		setLoggedIn(loginOk);

		// Navigation and message action
		if (loginOk) {
			facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO,
					"Successful Login!", null));
			if (requestedURL != null && !requestedURL.isEmpty()) {
				try {
					facesContext.getExternalContext().redirect(requestedURL);
					return null;
				} catch (IOException e) {
					log.severe("Failed to forward to original requested URL (" + requestedURL
							+ ")." + e.getMessage());
					facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO,
							"Failed to redirect after successfull login.", null));
				}
			} 
			return null;
		} else {
			facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR,
					"Invalid Login!", null));
			// stay on the same page
			return null;
		}
	}

}
